package com.example.foo.account.service;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.core.PageResult;
import com.example.core.Pageable;
import com.example.foo.account.model.Account;
import com.example.foo.account.model.AccountItem;
import com.example.foo.account.query.AccountItemQuery;
import com.example.foo.account.query.AccountQuery;
import com.example.foo.account.AccountException;
import com.example.foo.account.AccountHelper;
import com.example.foo.account.dao.AccountDao;
import com.example.foo.account.dao.AccountItemDao;

@org.springframework.stereotype.Service
public class AccountService {

	private final AccountDao accountDao;
	private final AccountItemDao accountItemDao;

	public AccountService(AccountDao accountDao,
			AccountItemDao accountItemDao) {
		this.accountDao = accountDao;
		this.accountItemDao = accountItemDao;
	}

	// ========== Account ==========

	public PageResult<Account> findAccounts(AccountQuery accountQuery, Pageable pageable) {
		return accountDao.findAccounts(accountQuery, pageable);
	}

	public List<Account> findAccountsByIds(List<Long> ids) {
		return accountDao.selectBatchIds(ids);
	}

	public Account findAccountById(Long id) {
		return accountDao.selectById(id);
	}

	public List<Account> findAccountsByUserId(Long userId) {
		return accountDao.selectList(new QueryWrapper<Account>()
				.eq("user_id", userId)
				);
	}

	public Map<String, BigDecimal> findAccountSummaryAsMapByUserId(Long userId) {
		List<Account> accounts = findAccountsByUserId(userId);
		Map<String, BigDecimal> accountSummaryMap = new HashMap<>(accounts.size());
		for (Account account : accounts) {
			String key = account.getAcctc().name() + "_" + account.getDrcrbal().name();
			accountSummaryMap.put(key, account.getTotal());
		}
		return accountSummaryMap;
	}

	public Account findAccountBalance(Account.Acctc acctc, Long userId) {
		return accountDao.selectOne(new QueryWrapper<Account>()
				.eq("acctc", acctc)
				.eq("drcrbal", Account.Drcrbal.bal)
				.eq("user_id", userId)
				);
	}

	public void createAccounts(Long userId) {
		LocalDateTime now = LocalDateTime.now();
		for (Account.Acctc acctc : Account.Acctc.values()) {
			for (Account.Drcrbal drcrbal : Account.Drcrbal.values()) {
				Account account = new Account();
				account.setAcctc(acctc);
				account.setDrcrbal(drcrbal);
				account.setTotal(BigDecimal.ZERO);
				account.setUserId(userId);
				account.setCreatedAt(now);
				accountDao.insert(account);
			}
		}
	}

	private int updateAccountTotalIncrement0(Long id, BigDecimal value) {
		Account account = new Account();
		account.setId(id);
		account.setTotal(value);
		account.setUpdatedAt(LocalDateTime.now());
		return accountDao.updateAccountTotalIncrement(account);
	}

	private int updateAccountTotalDecrement0(Long id, BigDecimal value) {
		Account account = new Account();
		account.setId(id);
		account.setTotal(value);
		account.setUpdatedAt(LocalDateTime.now());
		return accountDao.updateAccountTotalDecrement(account);
	}

	protected void updateAccountIncrement(Account.Acctc acctc, String name, BigDecimal value, Long userId, Long anotherUserId, String relType, Long relId) {
		if (value.compareTo(BigDecimal.ZERO) < 0) {
			throw new IllegalArgumentException("value must not be less than 0");
		}

		Account account = findAccountBalance(acctc, userId);
		if (account == null) {
			throw new AccountException(String.format("用户%s账户不存在", userId));
		}

		if (value.compareTo(BigDecimal.ZERO) > 0) {
			int affectedRows = updateAccountTotalIncrement0(account.getId(), value);
			if (affectedRows != 1) {
				throw new IllegalStateException("未知错误");
			}
		}

		createAccountItem(acctc, AccountItem.Drcr.incr, name, value, userId, anotherUserId, relType, relId);
	}

	protected void updateAccountDecrement(Account.Acctc acctc, String name, BigDecimal value, Long userId, Long anotherUserId, String relType, Long relId) {
		if (value.compareTo(BigDecimal.ZERO) < 0) {
			throw new IllegalArgumentException("value must not be less than 0");
		}

		Account account = findAccountBalance(acctc, userId);
		if (account == null) {
			throw new AccountException(String.format("用户%s账户不存在", userId));
		}
		if (account.getTotal().compareTo(value) < 0) {
			throw new AccountException(String.format("%s不足", acctc.text));
		}

		if (value.compareTo(BigDecimal.ZERO) > 0) {
			int affectedRows = updateAccountTotalDecrement0(account.getId(), value);
			if (affectedRows != 1) {
				throw new AccountException(String.format("%s不足", acctc.text));
			}
		}

		createAccountItem(acctc, AccountItem.Drcr.decr, name, value, userId, anotherUserId, relType, relId);
	}

	public void updateAccountIncrement(Account.Acctc acctc, String name, BigDecimal value, Long userId, String relType, Long relId) {
		updateAccountIncrement(acctc, name, value, userId, 0L, relType, relId);
	}

	public void updateAccountDecrement(Account.Acctc acctc, String name, BigDecimal value, Long userId, String relType, Long relId) {
		updateAccountDecrement(acctc, name, value, userId, 0L, relType, relId);
	}

	public void deleteAccountById(Long id) {
		accountDao.deleteById(id);
	}

	// ========== AccountItem ==========

	public PageResult<AccountItem> findAccountItems(AccountItemQuery accountItemQuery, Pageable pageable) {
		return accountItemDao.findAccountItems(accountItemQuery, pageable);
	}

	public List<AccountItem> findAccountItemsByIds(List<Long> ids) {
		return accountItemDao.selectBatchIds(ids);
	}

	public AccountItem findAccountItemById(Long id) {
		return accountItemDao.selectById(id);
	}

	public boolean findAccountItemCountExistByUserId(Long userId) {
		return accountItemDao.selectCount(new QueryWrapper<AccountItem>()
				.eq("user_id", userId)
				) > 0;
	}

	private void createAccountItem(Account.Acctc acctc, AccountItem.Drcr drcr, String name, BigDecimal value, Long userId, Long anotherUserId, String relType, Long relId) {
		LocalDate today = LocalDate.now();

		AccountItem accountItem = new AccountItem();
		accountItem.setAcctc(acctc);
		accountItem.setDrcr(drcr);
		accountItem.setName(name);
		accountItem.setValue(value);
		accountItem.setBalance(BigDecimal.ZERO);
		accountItem.setUserId(userId);
		accountItem.setAnotherUserId(anotherUserId);
		accountItem.setRelType(relType);
		accountItem.setRelId(relId);
		accountItem.setPeriodMonth(AccountHelper.toPeriodMonth(today));
		accountItem.setPeriodDay(AccountHelper.toPeriodDay(today));
		accountItem.setCreatedAt(LocalDateTime.now());
		accountItemDao.insert(accountItem);
	}

}
